// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Linq;
using CIP4.Common;

namespace CIP4.RulesEngine
{
    public class PolishExpressionType
    {
        #region operator+ implementations

        public static PolishExpressionType operator +(PolishExpressionType expression, ActionTypesEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, LogicalOperatorsEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, BooleanOperatorsEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, ArithmeticOperatorsEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, StringOperatorsEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, DateTimeOperatorsEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, CIValueType word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, ActionOperatorEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, BaseActionType word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, AtomSourceEnum word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, CIP4PrimitiveType word)
        {
            expression.Expression.Add(word);
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, bool word)
        {
            expression.Expression.Add(new BooleanAtomType(word));
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, string word)
        {
            expression.Expression.Add(new StringAtomType(word));
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, DateTime word)
        {
            expression.Expression.Add(new DateTimeAtomType(word));
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, long word)
        {
            expression.Expression.Add(new IntegerAtomType(word));
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, double word)
        {
            expression.Expression.Add(new FloatAtomType(word));
            return expression;
        }

        public static PolishExpressionType operator +(PolishExpressionType expression, decimal word)
        {
            expression.Expression.Add(new DecimalAtomType(word));
            return expression;
        }
        #endregion

        #region Type conversions

        private static void DoLogicalOperator(Stack<object> evalstack, object word)
        {
            try
            {
                switch ((LogicalOperatorsEnum)word)
                {
                    case LogicalOperatorsEnum.AND:
                        {
                            object p1 = evalstack.Pop();
                            if (!(p1 is IBooleanValue))
                                throw new ArgumentException(string.Format("{0} must be a boolean expression or a boolean value", p1));
                            object p2 = evalstack.Pop();
                            if (!(p2 is IBooleanValue))
                                throw new ArgumentException(string.Format("{0} must be a boolean expression or a boolean value", p2));
                            LogicalExpressionType lexp =
                                new LogicalExpressionType(
                                    LogicalOperatorsEnum.AND,
                                    new List<IBooleanValue> { (IBooleanValue)p1, (IBooleanValue)p2 });
                            evalstack.Push(lexp);
                            return;
                        }
                    case LogicalOperatorsEnum.OR:
                        {
                            object p1 = evalstack.Pop();
                            if (!(p1 is IBooleanValue))
                                throw new ArgumentException(string.Format("{0} must be a boolean expression or a boolean value", p1));
                            object p2 = evalstack.Pop();
                            if (!(p2 is IBooleanValue))
                                throw new ArgumentException(string.Format("{0} must be a boolean expression or a boolean value", p2));
                            LogicalExpressionType lexp =
                                new LogicalExpressionType(
                                    LogicalOperatorsEnum.OR,
                                    new List<IBooleanValue> { (IBooleanValue)p1, (IBooleanValue)p2 });
                            evalstack.Push(lexp);
                            return;
                        }
                    case LogicalOperatorsEnum.NOT:
                        {
                            object p1 = evalstack.Pop();
                            if (!(p1 is IBooleanValue))
                                throw new ArgumentException(string.Format("{0} must be a boolean expression or a boolean value", p1));
                            LogicalExpressionType lexp =
                                new LogicalExpressionType(
                                    LogicalOperatorsEnum.NOT,
                                    new List<IBooleanValue> { (IBooleanValue)p1 });
                            evalstack.Push(lexp);
                            return;
                        }
                    default:
                        throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
                }
            }
            catch (InvalidOperationException)
            {
                throw new ArgumentException(string.Format("There were not enough operands for {0}", word));
            }
        }

        private static CIP4PrimitiveType BaseCIValueType(CIValueType p1)
        {
            CIP4PrimitiveType t1 =
                p1 is ArithmeticAtomType ? CIP4PrimitiveType.CIP4Float :
                p1 is StringAtomType ? CIP4PrimitiveType.CIP4String :
                p1 is BooleanAtomType ? CIP4PrimitiveType.CIP4Boolean :
                p1 is DateTimeAtomType ? CIP4PrimitiveType.CIP4DateTime :
                p1 is ArithmeticExpressionType ? CIP4PrimitiveType.CIP4Float :
                p1 is StringExpressionType ? CIP4PrimitiveType.CIP4String :
                p1 is BooleanExpressionType ? CIP4PrimitiveType.CIP4Boolean :
                p1 is DateTimeExpressionType ? CIP4PrimitiveType.CIP4DateTime :
                CIP4PrimitiveType.CIP4Unknown;
            return t1;
        }

        private static void DoBooleanOperator(Stack<object> evalstack, object word)
        {
            switch ((BooleanOperatorsEnum)word)
            {
                case BooleanOperatorsEnum.EQL:
                case BooleanOperatorsEnum.GEQ:
                case BooleanOperatorsEnum.GTR:
                case BooleanOperatorsEnum.LEQ:
                case BooleanOperatorsEnum.LSS:
                case BooleanOperatorsEnum.NEQ:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is AtomType || p1 is ExpressionType))
                            throw new ArgumentException(string.Format("{0} must be an Atom or an Expression", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is AtomType || p2 is ExpressionType))
                            throw new ArgumentException(string.Format("{0} must be an Atom or an Expression", p2));
                        if (BaseCIValueType((CIValueType)p1) != BaseCIValueType((CIValueType)p2))
                            throw new ArgumentException(string.Format("{0} and {1} are not comparable types", p1, p2));
                        BooleanExpressionType bexp = new BooleanExpressionType(
                            (BooleanOperatorsEnum)word,
                            new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(bexp);
                        return;
                    }
                case BooleanOperatorsEnum.HASAVALUE:
                    {
                        object pt = evalstack.Pop();
                        if (!(pt is AtomType && ((AtomType)pt).AtomSource == AtomSourceEnum.namedValue))
                            throw new ArgumentException(string.Format("{0} must be a named value atom", pt));
                        BooleanExpressionType bexp = new BooleanExpressionType(BooleanOperatorsEnum.HASAVALUE, new List<CIValueType> { (AtomType)pt });
                        evalstack.Push(bexp);
                        return;
                    }
                case BooleanOperatorsEnum.CONTAINS:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.CONTAINS,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.ENDSWITH:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.ENDSWITH,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.EXACTMATCH:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.EXACTMATCH,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.LIKEMATCH:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.LIKEMATCH,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.MAXLENGTHTEST:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is IntegerAtomType))
                            throw new ArgumentException(string.Format("{0} must be an integer", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.MAXLENGTHTEST,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.MINLENGTHTEST:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is IntegerAtomType))
                            throw new ArgumentException(string.Format("{0} must be an integer", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.MINLENGTHTEST,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.PRECISIONTEST:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is IntegerAtomType))
                            throw new ArgumentException(string.Format("{0} must be an integer", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.PRECISIONTEST,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.STARTSWITH:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType || p2 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p2));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.STARTSWITH,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.CONTAINSHTML:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.CONTAINSHTML,
                                new List<CIValueType> { (CIValueType)p1 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.VALIDISBN:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.VALIDISBN,
                                new List<CIValueType> { (CIValueType)p1 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.VALIDUPC:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.VALIDUPC,
                                new List<CIValueType> { (CIValueType)p1 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.VALIDURI:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType || p1 is DecimalAtomType))
                            throw new ArgumentException(string.Format("{0} must be a string or a decimal", p1));
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.VALIDURI,
                                new List<CIValueType> { (CIValueType)p1 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.ALL:
                    {
                        object p1 = evalstack.Pop();
                        object p2 = evalstack.Pop();
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.ALL,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                case BooleanOperatorsEnum.SOME:
                    {
                        object p1 = evalstack.Pop();
                        object p2 = evalstack.Pop();
                        BooleanExpressionType lexp =
                            new BooleanExpressionType(
                                BooleanOperatorsEnum.SOME,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(lexp);
                        return;
                    }
                default:
                    throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
            }
        }

        private static void DoArithmeticOperator(Stack<object> evalstack, object word)
        {
            switch ((ArithmeticOperatorsEnum)word)
            {
                case ArithmeticOperatorsEnum.PLUS:
                case ArithmeticOperatorsEnum.MINUS:
                case ArithmeticOperatorsEnum.TIMES:
                case ArithmeticOperatorsEnum.DIVIDE:
                case ArithmeticOperatorsEnum.EXPON:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is AtomType || p1 is ExpressionType))
                            throw new ArgumentException(string.Format("{0} must be an Atom or an Expression", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is AtomType || p2 is ExpressionType))
                            throw new ArgumentException(string.Format("{0} must be an Atom or an Expression", p2));
                        if (BaseCIValueType((CIValueType)p1) != CIP4PrimitiveType.CIP4Float)
                            throw new ArgumentException(string.Format("{0} must be an arithmetic value", p1));
                        if (BaseCIValueType((CIValueType)p2) != CIP4PrimitiveType.CIP4Float)
                            throw new ArgumentException(string.Format("{0} must be an arithmetic value", p2));
                        ArithmeticExpressionType aexp = new ArithmeticExpressionType(
                            (ArithmeticOperatorsEnum)word,
                            new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(aexp);
                        return;
                    }
                default:
                    throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
            }
        }

        private static void DoStringOperator(Stack<object> evalstack, object word)
        {
            switch ((StringOperatorsEnum)word)
            {
                case StringOperatorsEnum.CONCAT:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p2));
                        StringExpressionType sexp =
                            new StringExpressionType(
                                StringOperatorsEnum.CONCAT,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(sexp);
                        return;
                    }
                case StringOperatorsEnum.GETREGEXMATCH:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p2));
                        StringExpressionType sexp =
                            new StringExpressionType(
                                StringOperatorsEnum.GETREGEXMATCH,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(sexp);
                        return;
                    }
                case StringOperatorsEnum.REMOVEHTML:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p1));
                        StringExpressionType sexp =
                            new StringExpressionType(
                                StringOperatorsEnum.REMOVEHTML,
                                new List<CIValueType> { (CIValueType)p1 });
                        evalstack.Push(sexp);
                        return;
                    }
                case StringOperatorsEnum.REPLACE:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is StringAtomType || p2 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p2));
                        object p3 = evalstack.Pop();
                        if (!(p3 is StringAtomType || p3 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p3));
                        StringExpressionType sexp =
                            new StringExpressionType(
                                StringOperatorsEnum.CONCAT,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2, (CIValueType)p3 });
                        evalstack.Push(sexp);
                        return;
                    }
                case StringOperatorsEnum.SUBSTRING:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is StringAtomType || p1 is StringExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a string", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is IntegerAtomType || p2 is ArithmeticExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a number", p2));
                        object p3 = evalstack.Pop();
                        if (!(p3 is IntegerAtomType || p3 is ArithmeticExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a number", p3));
                        StringExpressionType sexp =
                            new StringExpressionType(
                                StringOperatorsEnum.SUBSTRING,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2, (CIValueType)p3 });
                        evalstack.Push(sexp);
                        return;
                    }
                default:
                    throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
            }
        }

        private static void DoDateTimeOperator(Stack<object> evalstack, object word)
        {
            switch ((DateTimeOperatorsEnum)word)
            {
                case DateTimeOperatorsEnum.ADDDAYS:
                case DateTimeOperatorsEnum.ADDHOURS:
                case DateTimeOperatorsEnum.ADDMINUTES:
                case DateTimeOperatorsEnum.ADDMONTHS:
                case DateTimeOperatorsEnum.ADDYEARS:
                    {
                        object p1 = evalstack.Pop();
                        if (!(p1 is DateTimeAtomType || p1 is DateTimeExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a datetime", p1));
                        object p2 = evalstack.Pop();
                        if (!(p2 is IntegerAtomType || p2 is ArithmeticExpressionType))
                            throw new ArgumentException(string.Format("{0} must be a number", p2));
                        DateTimeExpressionType dexp =
                            new DateTimeExpressionType(
                                (DateTimeOperatorsEnum)word,
                                new List<CIValueType> { (CIValueType)p1, (CIValueType)p2 });
                        evalstack.Push(dexp);
                        return;
                    }
                default:
                    throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
            }
        }

        private static void DoActionOperator(Stack<object> evalstack, object word)
        {
            switch ((ActionOperatorEnum)word)
            {
                case ActionOperatorEnum.ASSIGN:
                    {
                        object pt = evalstack.Pop();
                        if (!(pt is AtomType && ((AtomType)pt).AtomSource == AtomSourceEnum.namedValue))
                            throw new ArgumentException(string.Format("{0} must be a named value atom", pt));
                        object pv = evalstack.Pop();
                        if (!(pv is AtomType || pv is ExpressionType))
                            throw new ArgumentException(string.Format("{0} must be an atom or an expression", pv));
                        ActionType action = new ActionType(ActionOperatorEnum.ASSIGN, new List<CIValueType> { (CIValueType)pt, (CIValueType)pv });
                        evalstack.Push(action);
                        return;
                    }
                case ActionOperatorEnum.ADDMESSAGE:
                    {
                        object id = evalstack.Pop();
                        if (!(id is string || id is StringAtomType))
                            throw new ArgumentException(string.Format("Message ID {0} must be a string or a StringAtom", id));
                        if (id is string)
                            id = new StringAtomType(id.ToString());
                        object org = evalstack.Pop();
                        if (!(org is string || org is StringAtomType))
                            throw new ArgumentException(string.Format("Organization domain {0} must be a string or a StringAtom", id));
                        if (org is string)
                            org = new StringAtomType(org.ToString());
                        object sev = evalstack.Pop();
                        if (!(sev is string || sev is StringAtomType))
                            throw new ArgumentException(string.Format("Severity {0} must be a string or a StringAtom", sev));
                        if (sev is string)
                            sev = new StringAtomType(sev.ToString());
                        object msg = evalstack.Pop();
                        if (!(msg is string || msg is StringAtomType))
                            throw new ArgumentException(string.Format("Message {0} must be a string or a StringAtom", msg));
                        if (msg is string)
                            msg = new StringAtomType(msg.ToString());
                        ActionType action = new ActionType(ActionOperatorEnum.ADDITEMMESSAGE, new List<CIValueType> { (CIValueType)id, (CIValueType)org, (CIValueType)sev, (CIValueType)msg });
                        while (evalstack.Peek() is string || evalstack.Peek() is StringAtomType)
                        {
                            object para = evalstack.Pop();
                            if (para is string)
                                para = new StringAtomType(para.ToString());
                            action.Parameters.Add((CIValueType)para);
                        }
                        evalstack.Push(action);
                        return;
                    }
                case ActionOperatorEnum.ADDITEMMESSAGE:
                    {
                        object sev = evalstack.Pop();
                        if (!(sev is string || sev is StringAtomType))
                            throw new ArgumentException(string.Format("Severity {0} must be a string or a StringAtom", sev));
                        if (sev is string)
                            sev = new StringAtomType(sev.ToString());
                        object msg = evalstack.Pop();
                        if (!(msg is string || msg is StringAtomType))
                            throw new ArgumentException(string.Format("Message {0} must be a string or a StringAtom", msg));
                        if (msg is string)
                            msg = new StringAtomType(msg.ToString());
                        ActionType action = new ActionType(ActionOperatorEnum.ADDITEMMESSAGE, new List<CIValueType> { (CIValueType)sev, (CIValueType)msg });
                        evalstack.Push(action);
                        return;
                    }
                case ActionOperatorEnum.ADDPROPERTYMESSAGE:
                    {
                        object sev = evalstack.Pop();
                        if (!(sev is string || sev is StringAtomType))
                            throw new ArgumentException(string.Format("Severity {0} must be a string or a StringAtom", sev));
                        if (sev is string)
                            sev = new StringAtomType(sev.ToString());
                        object msg = evalstack.Pop();
                        if (!(msg is string || msg is StringAtomType))
                            throw new ArgumentException(string.Format("Message {0} must be a string or a StringAtom", msg));
                        if (msg is string)
                            msg = new StringAtomType(msg.ToString());
                        object prop = evalstack.Pop();
                        if (!(prop is string || prop is StringAtomType))
                            throw new ArgumentException(string.Format("Severity {0} must be a string or a StringAtom", prop));
                        if (prop is string)
                            prop = new StringAtomType(prop.ToString());
                        ActionType action = new ActionType(ActionOperatorEnum.ADDPROPERTYMESSAGE, new List<CIValueType> { (CIValueType)sev, (CIValueType)msg, (CIValueType)prop });
                        evalstack.Push(action);
                        return;
                    }
                case ActionOperatorEnum.CREATEITEM:
                case ActionOperatorEnum.REMOVEFIELD:
                case ActionOperatorEnum.RUN:
                    throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
                default:
                    throw new ArgumentException(string.Format("{0} is an unsupported operator on this context", word));
            }
        }

        private static void DoAtomSource(Stack<object> evalstack, object word)
        {
            switch ((AtomSourceEnum)word)
            {
                case AtomSourceEnum.namedValue:
                    object pn = evalstack.Pop();
                    if (!((pn is StringAtomType && ((StringAtomType)pn).AtomSource == AtomSourceEnum.literal) || pn is string))
                        throw new ArgumentException(string.Format("{0} must be a string literal", pn));
                    string atomName = pn is string? pn.ToString(): ((StringAtomType)pn).AtomValue;
                    CIP4PrimitiveType atomType = CIP4PrimitiveType.CIP4String;
                    if (evalstack.Peek() is CIP4PrimitiveType)
                        atomType = (CIP4PrimitiveType)evalstack.Pop();
                    AtomType newAtom;
                    switch (atomType)
                    {
                        case CIP4PrimitiveType.CIP4String:
                            newAtom = new StringAtomType(AtomSourceEnum.namedValue, atomName);
                            break;
                        case CIP4PrimitiveType.CIP4Boolean:
                            newAtom = new BooleanAtomType(AtomSourceEnum.namedValue, atomName);
                            break;
                        case CIP4PrimitiveType.CIP4DateTime:
                            newAtom = new DateTimeAtomType(AtomSourceEnum.namedValue, atomName);
                            break;
                        case CIP4PrimitiveType.CIP4Decimal:
                            newAtom = new DecimalAtomType(AtomSourceEnum.namedValue, atomName);
                            break;
                        case CIP4PrimitiveType.CIP4Float:
                            newAtom = new FloatAtomType(AtomSourceEnum.namedValue, atomName);
                            break;
                        case CIP4PrimitiveType.CIP4Integer:
                            newAtom = new IntegerAtomType(AtomSourceEnum.namedValue, atomName);
                            break;
                        default:
                            throw new ArgumentException(string.Format("{0} unrecognized atom type", atomType));
                    }
                    evalstack.Push(newAtom);
                    return;
            }
        }

        private static void DoBaseAction(Stack<object> evalstack, object word)
        {
            if (word is ActionType)
                DoAction(evalstack, word);
            else if (word is ConditionalActionType)
                DoConditionalAction(evalstack, word);
            else if (word is ActionSequenceType)
                DoActionSequence(evalstack, word);
            else
                throw new ArgumentException(string.Format("{0} is not a recognized action type", word));
        }

        private static void DoConditionalAction(Stack<object> evalstack, object word)
        {
            if (((ConditionalActionType)word).Condition == null)
            {
                object pc = evalstack.Pop();
                if (!(pc is IBooleanValue))
                    throw new ArgumentException(string.Format("{0} must be a boolean value or boolean expression", pc));
                ((ConditionalActionType)word).Condition = (IBooleanValue)pc;
            }
            if (((ConditionalActionType)word).ThenAction == null)
            {
                object pa = evalstack.Pop();
                if (!(pa is BaseActionType))
                    throw new ArgumentException(string.Format("{0} must be an action", pa));
                ((ConditionalActionType)word).ThenAction = (BaseActionType)pa;
            }
            evalstack.Push(word);
        }

        private static void DoActionSequence(Stack<object> evalstack, object word)
        {
            evalstack.Push(word);
        }

        private static void DoAction(Stack<object> evalstack, object word)
        {
            if (((ActionType)word).Parameters.Count == 0)
                DoActionOperator(evalstack, ((ActionType)word).ActionOperator);
            else
                evalstack.Push(word);
        }

        /// <summary>
        /// Don't allow partial atoms or expressions - if anything's missing, it's an 
        /// error that will only be picked up when the final expression is checked
        /// </summary>
        /// <param name="evalstack"></param>
        /// <param name="word"></param>
        private static void DoCIValue(Stack<object> evalstack, object word)
        {
            evalstack.Push(word);
        }

        private static void DoActionType(Stack<object> evalstack, object word)
        {
            switch ((ActionTypesEnum)word)
            {
                case ActionTypesEnum.doAction:
                    DoActionSequenceOperator(evalstack, word);
                    return;
                case ActionTypesEnum.elseIfAction:
                case ActionTypesEnum.ifAction:
                    DoConditionalActionOperator(evalstack, word);
                    return;
                case ActionTypesEnum.endoAction:
                    evalstack.Push(word);
                    return;
                default:
                    throw new ArgumentException(string.Format("{0} is not expected in this context", word));
            }
        }

        /// <summary>
        /// doAction has been encountered in the expression - go through the evalStack looking for endoAction
        /// popping off BaseAction objects - encountering a non-BaseAction object is an error
        /// </summary>
        /// <param name="evalstack"></param>
        /// <param name="word"></param>
        private static void DoActionSequenceOperator(Stack<object> evalstack, object word)
        {
            if (!(word is ActionTypesEnum) || (ActionTypesEnum)word != ActionTypesEnum.doAction)
                throw new ArgumentException(string.Format("{0} is not valid in this context", word));
            ActionSequenceType act = new ActionSequenceType();
            object nextWord = evalstack.Pop();
            do
            {
                if (nextWord is ActionTypesEnum && (ActionTypesEnum)nextWord == ActionTypesEnum.endoAction)
                    break;
                if (!(nextWord is BaseActionType))
                    throw new ArgumentException(string.Format("expecting an action, found {0}", nextWord));
                act.Actions.Add((BaseActionType)nextWord);
                nextWord = evalstack.Pop();
            } while (true);

            evalstack.Push(act);
        }

        private static void DoConditionalActionOperator(Stack<object> evalstack, object word)
        {
            ConditionalActionType act = new ConditionalActionType();
            if (!(word is ActionTypesEnum) || !((ActionTypesEnum)word == ActionTypesEnum.ifAction || (ActionTypesEnum)word == ActionTypesEnum.elseIfAction))
                throw new ArgumentException(string.Format("{0} is not valid in this context", word));
            object nextWord = evalstack.Pop();
            if (!(nextWord is IBooleanValue))
                throw new ArgumentException(string.Format("Expecting a condition not {0}", nextWord));
            act.Condition = (IBooleanValue)nextWord;

            nextWord = evalstack.Pop();
            if (!(nextWord is BaseActionType))
                throw new ArgumentException(string.Format("Expecting an action not {0}", nextWord));
            act.ThenAction = (BaseActionType)nextWord;
            if ((ActionTypesEnum)word == ActionTypesEnum.elseIfAction)
            {
                nextWord = evalstack.Pop();
                if (!(nextWord is BaseActionType))
                    throw new ArgumentException(string.Format("Expecting an action not {0}", nextWord));
                act.ElseAction = (BaseActionType)nextWord;
            }
            evalstack.Push(act);
        }

        private static void DoExpression(Stack<object> evalstack, PolishExpressionType exp)
        {
            foreach (object word in exp.Expression)
            {
                if (word is LogicalOperatorsEnum)
                {
                    DoLogicalOperator(evalstack, word);
                }
                else if (word is BooleanOperatorsEnum)
                {
                    DoBooleanOperator(evalstack, word);
                }
                else if (word is ArithmeticOperatorsEnum)
                {
                    DoArithmeticOperator(evalstack, word);
                }
                else if (word is StringOperatorsEnum)
                {
                    DoStringOperator(evalstack, word);
                }
                else if (word is DateTimeOperatorsEnum)
                {
                    DoDateTimeOperator(evalstack, word);
                }
                else if (word is ActionOperatorEnum)
                {
                    DoActionOperator(evalstack, word);
                }
                else if (word is AtomSourceEnum)
                {
                    DoAtomSource(evalstack, word);
                }
                else if (word is ActionTypesEnum)
                {
                    DoActionType(evalstack, word);
                }
                else if (word is CIValueType)
                {
                    DoCIValue(evalstack, word);
                }
                else if (word is BaseActionType)
                {
                    DoBaseAction(evalstack, word);
                }
                else
                    evalstack.Push(word);
            }
        }

        public static implicit operator CIValueType(PolishExpressionType exp)
        {
            Stack<object> evalstack = new Stack<object>();
            DoExpression(evalstack, exp);
            if (evalstack.Count != 1)
                throw new ArgumentException("{0} is not a valid expression - it must consist of exactly one atom or expression", exp.ToString());
            object word = evalstack.Pop();
            if (!(word is CIValueType))
                throw new ArgumentException("{0} is not a valid expression - it must consist of exactly one atom or expression", exp.ToString());
            return (CIValueType)word;
        }

        public static implicit operator ActionSequenceType(PolishExpressionType exp)
        {
            Stack<object> evalstack = new Stack<object>();
            DoExpression(evalstack, exp);
            ActionSequenceType result;
            if (evalstack.Count > 1)
            {
                result = new ActionSequenceType();
                while (evalstack.Count > 0)
                {
                    object word = evalstack.Pop();
                    if (!(word is BaseActionType))
                        throw new ArgumentException("{0} is not a valid expression - it must consist of one or more actions to be convertible to an actionsequence", exp.ToString());
                    result.Actions.Add((BaseActionType)word);
                }
            }
            else if (evalstack.Count == 1)
            {
                object word = evalstack.Pop();
                if (!(word is BaseActionType))
                    throw new ArgumentException("{0} is not a valid expression - it must consist of one or more actions to be convertible to an actionsequence", exp.ToString());
                result = word is ActionSequenceType ?
                    (ActionSequenceType)word :
                    new ActionSequenceType(new List<BaseActionType> { (BaseActionType)word });
            }
            else
                throw new ArgumentException("{0} is not a valid expression - it must consist of one or more actions to be convertible to an actionsequence", exp.ToString());
            return result;
        }

        public static implicit operator PolishExpressionType(BaseActionType action)
        {
            return new PolishExpressionType(action);
        }

        public static implicit operator PolishExpressionType(CIValueType value)
        {
            return new PolishExpressionType(value);
        }

        #endregion

        private readonly List<object> m_Expression;

        public List<object> Expression
        {
            get { return m_Expression; }
        }

        private PolishExpressionType(BaseActionType action)
        {
            m_Expression = new List<object>();
            Unravel(action);
        }

        private PolishExpressionType(CIValueType value)
        {
            m_Expression = new List<object>();
            Unravel(value);
        }

        public PolishExpressionType()
        {
            m_Expression = new List<object>();
        }

        #region Unravelling native rules engine actions and values

        /// <summary>
        /// Everything has to be done backwards - hence all the for (int i... loops rather than foreach
        /// and operators get dealt with last rather than first so, for example, the ActionType code
        /// unravels the parameters into the expression list before putting in the operator
        /// </summary>
        /// <param name="action"></param>
        private void Unravel(BaseActionType action)
        {
            if (action is ActionSequenceType)
            {
                // don't put in enddoactio/doaction if there is only one action in the sequence
                if (((ActionSequenceType)action).Actions.Count == 1)
                    Unravel(((ActionSequenceType)action).Actions[0]);
                else
                {
                    Expression.Add(ActionTypesEnum.endoAction);
                    for (int i = ((ActionSequenceType)action).Actions.Count - 1; i >= 0; i--)
                        Unravel(((ActionSequenceType)action).Actions[i]);
                    Expression.Add(ActionTypesEnum.doAction);
                }
            }
            else if (action is ConditionalActionType)
            {
                if (((ConditionalActionType)action).ElseActions.Count > 0)
                    Unravel(((ConditionalActionType)action).ElseAction);
                if (((ConditionalActionType)action).Actions.Count > 0)
                    Unravel(((ConditionalActionType)action).ThenAction);
                else
                    throw new ArgumentException("{0} cannot be made into a Polish expression because the current implementation requires then Actions", action.ToString());
                Unravel((CIValueType)((ConditionalActionType)action).Condition);
                if (((ConditionalActionType)action).ElseActions.Count > 0)
                    Expression.Add(ActionTypesEnum.elseIfAction);
                else
                    Expression.Add(ActionTypesEnum.ifAction);
            }
            else if (action is ActionType)
            {
                for (int i = ((ActionType)action).Parameters.Count - 1; i >= 0; i--)
                    Unravel(((ActionType)action).Parameters[i]);
                Expression.Add(((ActionType)action).ActionOperator);
            }
            else
                throw new ArgumentException("{0} is not a recognized action type", action.ToString());
        }

        private void Unravel(CIValueType value)
        {
            if (value is AtomType)
                UnravelAtom((AtomType)value);
            else
                UnravelExpression((ExpressionType)value);
        }

        private void UnravelAtom(AtomType atom)
        {
            if (atom.AtomSource == AtomSourceEnum.namedValue)
            {
                if (BaseCIValueType(atom) != CIP4PrimitiveType.CIP4String)
                    Expression.Add(BaseCIValueType(atom));
                // NB: although a String object would work here - use StringAtomType as it is required for roundtripping
                Expression.Add(new StringAtomType(atom.AtomName));
                Expression.Add(AtomSourceEnum.namedValue);
            }
            else if (atom.AtomSource == AtomSourceEnum.literal)
            {
                Expression.Add(atom);
            }
            else
                throw new ArgumentException("{0} does not have a recognized AtomSource type", atom.ToString());
        }

        private void UnravelExpression(ExpressionType expression)
        {
            for (int i = expression.Operands.Count - 1; i >= 0; i--)
                Unravel(expression.Operands[i]);
            if (expression is StringExpressionType)
                Expression.Add(((StringExpressionType)expression).ExpressionOperator);
            else if (expression is LogicalExpressionType)
                Expression.Add(((LogicalExpressionType)expression).ExpressionOperator);
            else if (expression is BooleanExpressionType)
                Expression.Add(((BooleanExpressionType)expression).ExpressionOperator);
            else if (expression is DateTimeExpressionType)
                Expression.Add(((DateTimeExpressionType)expression).ExpressionOperator);
            else if (expression is ArithmeticExpressionType)
                Expression.Add(((ArithmeticExpressionType)expression).ExpressionOperator);
            else
                throw new ArgumentException("{0} is not a recognized expression type", expression.ToString());
        }
        #endregion

        public override string ToString()
        {
            return m_Expression.Aggregate(":", (current, word) => current + (" " + word));
        }
    }
}
